/*
 * @Descripttion: GGB
 * @version: 1.0
 * @Author: GGB
 * @Date: 2022-02-16 06:27:26
 * @LastEditors: GGB
 * @LastEditTime: 2022-02-17 06:55:14
 */
import * as THREE from 'three'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import Bullet from './Bullet';
MTLLoader

export default class BlasterScene extends THREE.Scene {
    private readonly mtlLoader = new MTLLoader();// mtl 加载器
    private readonly objLoader = new OBJLoader();// obj加载器
    private readonly camera: THREE.PerspectiveCamera;// 相机
    private readonly keyDown = new Set<string>();// 鼠标事件

    private blaster?: THREE.Group;// 枪
    private bulletMtl?: MTLLoader.MaterialCreator;

    private directionVector = new THREE.Vector3(); // 方向向量

    private bullets: Bullet[] = [];// 子弹数组
    private targets: THREE.Group[] = []

    constructor(camera: THREE.PerspectiveCamera) {
        super()
        this.camera = camera
    }

    /**
     * 初始化场景
     */
    async initialise() {

        // 创建目标材质
        const targetMtl = await this.mtlLoader.loadAsync('assets/targetA.mtl')
        targetMtl.preload()

        this.bulletMtl = await this.mtlLoader.loadAsync('assets/foamBulletB.mtl')
        targetMtl.preload()

        // create the 4 targets
        const t1 = await this.createTarget(targetMtl)
        t1.position.x = -1
        t1.position.z = -3

        const t2 = await this.createTarget(targetMtl)
        t2.position.x = 1
        t2.position.z = -3

        const t3 = await this.createTarget(targetMtl)
        t3.position.x = 2
        t3.position.z = -3

        const t4 = await this.createTarget(targetMtl)
        t4.position.x = -2
        t4.position.z = -3

        this.add(t1, t2, t3, t4)
        this.targets.push(t1, t2, t3, t4)

        this.blaster = await this.createBlaster()
        this.add(this.blaster)

        this.blaster.position.z = 3
        this.blaster.add(this.camera)

        this.camera.position.z = 1
        this.camera.position.y = 0.5


        const light = new THREE.DirectionalLight(0xffffff, 1)
        light.position.set(0, 4, 2)

        this.add(light)

        document.addEventListener('keydown', this.handleKeyDown)
        document.addEventListener('keyup', this.handleKeyUp)
    }

    /**
     * 按键按下事件
     * @param event 
     */

    private handleKeyDown = (event: KeyboardEvent) => {
        this.keyDown.add(event.key.toLowerCase())
    }

    /**
     * 按键抬起事件
     * @param event 
     */

    private handleKeyUp = (event: KeyboardEvent) => {
        this.keyDown.delete(event.key.toLowerCase())
        if (event.key === ' ') {
            this.createBullet()
        }
    }

    private updateInput() {
        if (!this.blaster) {
            return
        }
        const shiftKey = this.keyDown.has('shift')
        if (!shiftKey) {
            if (this.keyDown.has('a') || this.keyDown.has('arrowleft')) {
                this.blaster.rotateY(0.02)
            }
            else if (this.keyDown.has('d') || this.keyDown.has('arrowright')) {
                this.blaster.rotateY(-0.02)
            }
        }

        const dir = this.directionVector
        this.camera.getWorldDirection(dir)

        const speed = 0.1
        if (this.keyDown.has("w") || this.keyDown.has('arrowup')) {
            // this.blaster.translateOnAxis(dir, speed)
            this.blaster.position.add(dir.clone().multiplyScalar(speed))
        }
        else if (this.keyDown.has("s") || this.keyDown.has('arrowdown')) {
            this.blaster.position.add(dir.clone().multiplyScalar(-speed))
        }

        if (shiftKey) {
            const strafeDir = dir.clone()
            const upVector = new THREE.Vector3(0, 1, 0)
            ///multiplyScalar方法用来将三维向量的(x,y,z)坐标值直接与参数s相乘.并返回新的坐标值的三维向量. 
            /// NOTE:这里与multiply()方法不同的是,这里传递的参数scalar是一个标量,而multiply()方法的参数v是一个三维向量. 
            ///<summary>multiplyScalar</summary>  
            ///<param name ="scalar" type="number">与当前对象(x,y,z)值相乘的标量,数值</param>  
            ///<returns type="Vector3">返回新坐标值的三维向量</returns>  
            // multiplyScalar: function ( scalar ) {  

            //     this.x *= scalar;  
            //     this.y *= scalar;  
            //     this.z *= scalar;  

            //     return this;    //返回新坐标值的三维向量  

            // }

            ///<summary>applyMatrix3</summary>  
            ///<param name ="axis" type="Vector3">三维向量</param>  
            ///<param name ="angle" type="Matrix3">3x3矩阵</param>  
            ///<returns type="Vector3">返回变换后的三维向量</returns>  
            // applyAxisAngle: function () {  

            //     var quaternion;  

            //     return function ( axis, angle ) {  

            //         if ( quaternion === undefined ) quaternion = new THREE.Quaternion();  

            //         this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); //实际调用的四元数下面的方法.setFromAxisAngle()  

            //         return this;    //返回变换后的三维向量  

            //     };  

            // }(),  
            if (this.keyDown.has('a') || this.keyDown.has('arrowleft')) {
                this.blaster.position.add(
                    strafeDir.applyAxisAngle(upVector, Math.PI * 0.5).multiplyScalar(speed)
                )
            }
            else if (this.keyDown.has('d') || this.keyDown.has('arrowright')) {
                this.blaster.position.add(
                    strafeDir.applyAxisAngle(upVector, Math.PI * -0.5).multiplyScalar(speed)
                )
            }
        }
    }


    /**
     * 创建目标
     * @param mtl 
     * @returns 
     */
    private async createTarget(mtl: MTLLoader.MaterialCreator) {
        this.objLoader.setMaterials(mtl)
        const modelRoot = await this.objLoader.loadAsync('assets/targetA.obj')
        modelRoot.rotateY(Math.PI * 0.5)
        return modelRoot
    }

    /**
     * 创建子弹
     */

    private async createBlaster() {
        const mtl = await this.mtlLoader.loadAsync('assets/blasterG.mtl')
        mtl.preload()

        this.objLoader.setMaterials(mtl)
        const modelRoot = await this.objLoader.loadAsync('assets/blasterG.obj')
        return modelRoot
    }

    private async createBullet() {
        if (!this.blaster) {
            return
        }
        if (this.bulletMtl) {
            this.objLoader.setMaterials(this.bulletMtl)

        }
        const bulletModel = await this.objLoader.loadAsync('assets/foamBulletB.obj')
        // getWorldDirection ( target : Vector3 ) : Vector3 target — 调用该函数的结果将复制给该Vector3对象。
        this.camera.getWorldDirection(this.directionVector)
        // 计算和世界轴对齐的一个对象 Object3D （含其子对象）的包围盒，计算对象和子对象的世界坐标变换。
        const aabb = new THREE.Box3().setFromObject(bulletModel)
        // 包围盒的大小
        const size = aabb.getSize(new THREE.Vector3())
        const vec = this.blaster.position.clone()
        vec.y += 0.06

        bulletModel.position.add(vec.add(this.directionVector.clone().multiplyScalar(size.z * 0.5)))
        bulletModel.children.forEach(child => child.rotateX(Math.PI * -0.5))
        bulletModel.rotation.copy(this.blaster.rotation)
        this.add(bulletModel)

        const b = new Bullet(bulletModel)
        b.setVelocity(
            this.directionVector.x * 0.02,
            this.directionVector.y * 0.02,
            this.directionVector.z * 0.02
        )
        this.bullets.push(b)

    }
    /**
     * 更新子弹的位置
     */
    private updateBullets() {
        for (let i = 0; i < this.bullets.length; ++i) {
            const bullet = this.bullets[i]
            bullet.update()

            if (bullet.shouldRemove) {
                this.bullets.splice(i, 1)
                this.remove(bullet.group)
                i-- // 删除后,下标也要跟着删除
            } else {
                for (let j = 0; j < this.targets.length; ++j) {
                    const target = this.targets[j]
                    if (target.position.distanceToSquared(bullet.group.position) < 0.05) {
                        this.remove(bullet.group)
                        this.bullets.splice(i, 1)
                        i--
                        target.visible = false
                        setTimeout(() => {
                            target.visible = true
                        }, 1000)
                    }
                }
            }
        }
    }

    update() {
        this.updateInput()
        this.updateBullets()
    }
}